package ar.uba.fi.tonyvaliente.tree;

import java.util.NoSuchElementException;

import ar.uba.fi.tonyvaliente.files.File;
import ar.uba.fi.tonyvaliente.files.FileException;
import ar.uba.fi.tonyvaliente.files.FileRecord;
import ar.uba.fi.tonyvaliente.files.Persistor;
import ar.uba.fi.tonyvaliente.files.RecordPosition;

public class FileBPlusTree<K extends Comparable<K>, V> extends BPlusTree<K, V> {
	public static void create(int elementsPerNode, String pathName) throws BPlusTreeException {
		create(elementsPerNode, pathName, false);
	}

	public static void create(int elementsPerNode, String path, boolean overwrite) throws BPlusTreeException {
		if (overwrite) {
			java.io.File file = new java.io.File(path);
			if (file.exists() && file.isFile()) {
				if (!file.delete()) {
					throw new BPlusTreeException("Error deleting " + path);
				}
			}
		}

		try {
			File file = File.create(path, 4096);
			file.setProperty("ElementsPerNode", Integer.toString(elementsPerNode));
			file.setProperty("RootNodeReference:Block", "-1");
			file.setProperty("RootNodeReference:Record", "-1");
			file.close();
		} catch (FileException e) {
			e.printStackTrace();
			throw new BPlusTreeException("Error creating FileBPlusTree in " + path, e);
		}
	}

	public static <K extends Comparable<K>, V> FileBPlusTree<K, V> open(String path, Persistor<K> keyPersistor, Persistor<V> valuePersistor) throws BPlusTreeException {
		try {
			FileBPlusTree<K, V> tree = new FileBPlusTree<K, V>(File.open(path, false));
			tree.setKeyPersistor(keyPersistor);
			tree.setValuePersistor(valuePersistor);
			tree.setPath(path);
			return tree;
		} catch (FileException e) {
			e.printStackTrace();
			throw new BPlusTreeException("Error opening FileBPlusTree in " + path, e);
		}
	}

	private File file;
	private String path;
	private Persistor<K> keyPersistor;
	private Persistor<V> valuePersistor;

	private FileBPlusTree(File file) throws BPlusTreeException {
		super();
		this.setFile(file);
		this.setElementsPerNode(Integer.parseInt(file.getProperty("ElementsPerNode")));
	}
	
	@Override
	public void add(K key, V value) throws BPlusTreeException {
		super.add(key, value);
		
		this.rootNodeReference = null;
		this.reopen();
	}
	
	@Override
	public V get(K key) throws BPlusTreeException, NoSuchElementException {
		this.rootNodeReference = null;
		return super.get(key);
	}

	@Override
	protected void addDirtyNodeReference(BPlusTreeNodeReference<K, V> dirtyReference) {
		if (dirtyReference == null)
			return;
		FileBPlusTreeNodeReference<K, V> fileDirtyReference = (FileBPlusTreeNodeReference<K, V>)dirtyReference; 
		for (BPlusTreeNodeReference<K, V> reference : this.dirtyNodeReferences) {
			FileBPlusTreeNodeReference<K, V> fileReference = (FileBPlusTreeNodeReference<K, V>) reference;
			if (fileDirtyReference.getBlock() == fileReference.getBlock() && fileDirtyReference.getRecord() == fileReference.getRecord()) {
				this.dirtyNodeReferences.remove(fileReference);
				break;
			}
		}
		super.addDirtyNodeReference(dirtyReference);
	}

	@Override
	public void validate() throws BPlusTreeException {
		this.rootNodeReference = null;
		this.reopen();
		
		super.validate();
		
		this.rootNodeReference = null;
		this.reopen();
	}
	
	protected File getFile() {
		return this.file;
	}

	private void setFile(File file) {
		this.file = file;
	}

	private String getPath() {
		return this.path;
	}
	
	private void setPath(String path) {
		this.path = path;
	}
	
	@Override
	protected BPlusTreeNodeReference<K, V> getRootNodeReference() throws BPlusTreeException {
		if(this.rootNodeReference == null) {
			File file = this.getFile();
			int block = Integer.parseInt(file.getProperty("RootNodeReference:Block"));
			int record = Integer.parseInt(file.getProperty("RootNodeReference:Record"));
			if (block == -1 && record == -1) {
				RecordPosition rootRecordPosition = null;
				try {
					rootRecordPosition = file.addRecord(new FileRecord(new byte[] { 0 }));
				} catch (FileException e) {
					throw new BPlusTreeException("Error creating FileRecord", e);
				}
				FileBPlusTreeNodeReference<K, V> rootNodeReference = new FileBPlusTreeNodeReference<K, V>(this, rootRecordPosition.getBlockNumber(), rootRecordPosition.getOffset());
				LeafBPlusTreeNode<K, V> rootNode = new LeafBPlusTreeNode<K, V>(this, null);
				rootNodeReference.setNode(rootNode);
				rootNodeReference.updateNode();
				this.setRootNodeReference(rootNodeReference);
				this.rootNodeReference = rootNodeReference;
			} else {
				this.rootNodeReference = new FileBPlusTreeNodeReference<K, V>(this, block, record);
			}
		}
		return this.rootNodeReference;
	}

	@Override
	protected void setRootNodeReference(BPlusTreeNodeReference<K, V> reference) throws BPlusTreeException {
		File file = this.getFile(); 
		try {
			FileBPlusTreeNodeReference<K, V> rootNodeReference = (FileBPlusTreeNodeReference<K, V>)reference;
			if(rootNodeReference.getBlock() == -1 && rootNodeReference.getRecord() == -1)
			{
				try {
					RecordPosition rootRecordPosition = file.addRecord(new FileRecord(new byte[] { 0 }));
					rootNodeReference.setBlock(rootRecordPosition.getBlockNumber());
					rootNodeReference.setRecord(rootRecordPosition.getOffset());
				} catch (FileException e) {
					e.printStackTrace();
					throw new BPlusTreeException("Error saving new root", e);
				}
			}
			file.setProperty("RootNodeReference:Block", Integer.toString(rootNodeReference.getBlock()));
			file.setProperty("RootNodeReference:Record", Integer.toString(rootNodeReference.getRecord()));
			this.rootNodeReference = rootNodeReference;
		} catch (FileException e) {
			e.printStackTrace();
			// TODO: change exception's message
			throw new BPlusTreeException("1", e);
		}
	}

	public Persistor<K> getKeyPersistor() {
		return this.keyPersistor;
	}
	
	protected void setKeyPersistor(Persistor<K> keyPersistor) {
		this.keyPersistor = keyPersistor;
	}
	
	public Persistor<V> getValuePersistor() {
		return this.valuePersistor;
	}

	public void setValuePersistor(Persistor<V> valuePersistor) {
		this.valuePersistor = valuePersistor;
	}

	@Override
	public void close() throws BPlusTreeException {
		try {
			File file = this.getFile();
			file.flush();
			file.close();
		} catch (FileException e) {
			e.printStackTrace();
			throw new BPlusTreeException("Error closing FileBPlusTree", e);
		}
	}

	@Override
	protected BPlusTreeNodeReference<K, V> createReference() {
		return new FileBPlusTreeNodeReference<K, V>(this, -1, -1);
	}
	
	private void reopen()
	{
		try {
			this.getFile().close();
			this.setFile(File.open(this.getPath(), false));
		} catch (FileException e) {
			e.printStackTrace();
		}
	}
}
